﻿/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 * Marcus Cuda
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

namespace dnAnalytics.Math
{
    public static partial class ComplexMath
    {
        private static readonly Complex sNi = new Complex(0, -1);
        private static readonly Complex32 sNi32 = new Complex32(0, -1);


        /// <summary>Returns the cosine of a complex number.</summary>
        /// <returns>The cosine of a complex number.</returns>
        /// <param name="value">The complex number to compute the cosine of.</param>
        public static Complex Cos(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return Cosh(new Complex(-value.Imaginary, value.Real));
        }

        /// <summary>Returns the hyperbolic cosine of a complex number.</summary>
        /// <returns>The hyperbolic cosine of the complex number.</returns>
        /// <param name="value">The complex number to compute the hyperbolic cosine of.</param>
        public static Complex Cosh(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            if (Complex.IsInfinity(value))
            {
                return Complex.Infinity;
            }

            return new Complex(System.Math.Cos(value.Imaginary)*System.Math.Cosh(value.Real), System.Math.Sin(value.Imaginary)*System.Math.Sinh(value.Real));
        }

        /// <summary>Returns the exponential of a complex number.</summary>
        /// <returns>The exponential of the complex number.</returns>
        /// <param name="value">The complex number to compute the exponential of.</param>
        public static Complex Exp(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }

            double exp = System.Math.Exp(value.Real);
            if (value.Imaginary == 0)
            {
                return new Complex(exp);
            }
            return new Complex(exp*System.Math.Cos(value.Imaginary), exp*System.Math.Sin(value.Imaginary));
        }

        /// <returns>The exponentiation of the given base complex number to the given exponent.</returns>
        /// <summary>Computes the exponentiation a complex number.</summary>
        /// <param name="leftSide">The base.</param>
        /// <param name="rightSide">The exponent.</param>
        public static Complex Pow(Complex leftSide, Complex rightSide)
        {
            return Exp(rightSide*Log(leftSide));
        }

        /// <summary>Returns the logarithm of a complex number.</summary>
        /// <returns>The logarithm of the complex number.</returns>
        /// <param name="value">The complex number to compute the logarithm of.</param>
        public static Complex Log(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            double r = Absolute(value);
            double i = System.Math.Atan2(value.Imaginary, value.Real);
            if (i > System.Math.PI)
            {
                i -= (2.0*System.Math.PI);
            }
            return new Complex(System.Math.Log(r), i);
        }

        /// <summary>Returns the sine of a complex number.</summary>
        /// <returns>The sine of the complex number.</returns>
        /// <param name="value">The complex number to compute the sine of.</param>
        public static Complex Sin(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return new Complex(System.Math.Sin(value.Real)*System.Math.Cosh(value.Imaginary), System.Math.Cos(value.Real)*System.Math.Sinh(value.Imaginary));
        }

        /// <summary>Returns the hyperbolic sine of a complex number.</summary>
        /// <returns>The hyperbolic sine of the complex number.</returns>
        /// <param name="value">The complex number to compute the hyperbolic sine of.</param>
        public static Complex Sinh(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            if (value.Imaginary == 0)
            {
                return new Complex(System.Math.Sinh(value.Real));
            }
            if (value.Real == 0)
            {
                return new Complex(0, System.Math.Sin(value.Imaginary));
            }
            return new Complex(System.Math.Sinh(value.Real)*System.Math.Cos(value.Imaginary), System.Math.Cosh(value.Real)*System.Math.Sin(value.Imaginary));
        }

        /// <summary>Returns the square root of a complex number.</summary>
        /// <returns>The square root of the complex number.</returns>
        /// <param name="value">The complex number to compute the square root of.</param>
        public static Complex Sqrt(Complex value)
        {
            Complex result;
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }

            if (Complex.IsInfinity(value))
            {
                return Complex.Infinity;
            }

            if ((value.Real == 0.0) && (value.Imaginary == 0.0))
            {
                result = Complex.Zero;
            }
            else
            {
                double x = System.Math.Abs(value.Real);
                double y = System.Math.Abs(value.Imaginary);
                double t;
                if (x >= y)
                {
                    t = y/x;
                    t = System.Math.Sqrt(x)*System.Math.Sqrt(0.5*(1.0 + System.Math.Sqrt(1.0 + t*t)));
                }
                else
                {
                    t = x/y;
                    t = System.Math.Sqrt(y)*System.Math.Sqrt(0.5*(t + System.Math.Sqrt(1.0 + t*t)));
                }

                if (value.Real >= 0.0)
                {
                    result = new Complex(t, value.Imaginary/(2.0*t));
                }
                else if (value.Imaginary >= 0.0)
                {
                    result = new Complex(value.Imaginary/(2.0*t), t);
                }
                else
                {
                    result = new Complex(-value.Imaginary/(2.0*t), -t);
                }
            }

            return result;
        }

        /// <summary>Returns the tangent of a complex number.</summary>
        /// <returns>The tangent of the complex number.</returns>
        /// <param name="value">The complex number to compute the tangent of.</param>
        public static Complex Tan(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }

            double real = 2.0*value.Real;
            double imag = 2.0*value.Imaginary;
            double denom = System.Math.Cos(real) + System.Math.Cosh(imag);

            return new Complex(System.Math.Sin(real)/denom, System.Math.Sinh(imag)/denom);
        }

        /// <summary>Returns the hyperbolic tangent of a complex number.</summary>
        /// <returns>The hyperbolic tangent of the complex number.</returns>
        /// <param name="value">The complex number to compute the hyperbolic tangent of.</param>
        public static Complex Tanh(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }

            double real = 2.0*value.Real;
            double imag = 2.0*value.Imaginary;
            double denom = System.Math.Cosh(real) + System.Math.Cos(imag);

            return new Complex(System.Math.Sinh(real)/denom, System.Math.Sin(imag)/denom);
        }

        /// <summary>Returns the inverse sine of a complex number.</summary>
        /// <returns>The inverse sine of the complex number.</returns>
        /// <param name="value">The complex number to compute the inverse sine of.</param>
        public static Complex Asin(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }

            return sNi*Log(Complex.I*value + Sqrt(1 - value*value));
        }

        /// <summary>Returns the inverse cosine of a complex number.</summary>
        /// <returns>The inverse cosine of the complex number.</returns>
        /// <param name="value">The complex number to compute the inverse cosine of.</param>
        public static Complex Acos(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return sNi*Log(value + Complex.I*Sqrt(1 - value*value));
        }

        /// <summary>Returns the inverse tangent of a complex number.</summary>
        /// <returns>The inverse tangent of the complex number.</returns>
        /// <param name="value">The complex number to compute the inverse tangent of.</param>
        public static Complex Atan(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return (1/(Complex.I*2))*(Log((1 + Complex.I*value)) - Log((1 - Complex.I*value)));
        }

        /// <summary>Returns the inverse hyperbolic sine of a complex number.</summary>
        /// <returns>The inverse hyperbolic sine of the complex number.</returns>
        /// <param name="value">The complex number to compute the inverse hyperbolic sine of.</param>
        public static Complex Asinh(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return -Log(Sqrt(1 + value*value) - value);
        }

        /// <summary>Returns the inverse hyperbolic cosine of a complex number.</summary>
        /// <returns>The inverse hyperbolic cosine of the complex number.</returns>
        /// <param name="value">The complex number to compute the hyperbolic cosine of.</param>
        public static Complex Acosh(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return 2*Log((Sqrt(((value + 1)/2)) + Sqrt((value - 1)/2)));
        }

        /// <summary>Returns the inverse hyperbolic tangent of a complex number.</summary>
        /// <returns>The inverse hyperbolic tangent of the complex number.</returns>
        /// <param name="value">The complex number to compute the hyperbolic tangent of.</param>
        public static Complex Atanh(Complex value)
        {
            if (Complex.IsNaN(value))
            {
                return Complex.NaN;
            }
            return .5*(Log(1 + value) - Log(1 - value));
        }

        /// <summary>Returns the cosine of a Complex32 number.</summary>
        /// <returns>The cosine of a Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the cosine of.</param>
        public static Complex32 Cos(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return Cosh(new Complex32(-value.Imaginary, value.Real));
        }

        /// <summary>Returns the hyperbolic cosine of a Complex32 number.</summary>
        /// <returns>The hyperbolic cosine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the hyperbolic cosine of.</param>
        public static Complex32 Cosh(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            if (Complex32.IsInfinity(value))
            {
                return Complex32.Infinity;
            }

            return new Complex32((float) (System.Math.Cos(value.Imaginary)*System.Math.Cosh(value.Real)), (float) (System.Math.Sin(value.Imaginary)*System.Math.Sinh(value.Real)));
        }

        /// <summary>Returns the exponential of a Complex32 number.</summary>
        /// <returns>The exponential of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the exponential of.</param>
        public static Complex32 Exp(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }

            float exp = (float) System.Math.Exp(value.Real);
            if (value.Imaginary == 0)
            {
                return new Complex32(exp);
            }
            return new Complex32(exp*(float) System.Math.Cos(value.Imaginary), exp*(float) System.Math.Sin(value.Imaginary));
        }

        /// <returns>The exponentiation of the given base Complex32 number to the given exponent.</returns>
        /// <summary>Computes the exponentiation a Complex32 number.</summary>
        /// <param name="leftSide">The base.</param>
        /// <param name="rightSide">The exponent.</param>
        public static Complex32 Pow(Complex32 leftSide, Complex32 rightSide)
        {
            return Exp(rightSide*Log(leftSide));
        }

        /// <summary>Returns the logarithm of a Complex32 number.</summary>
        /// <returns>The logarithm of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the logarithm of.</param>
        public static Complex32 Log(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            double r = Absolute(value);
            double i = System.Math.Atan2(value.Imaginary, value.Real);
            if (i > System.Math.PI)
            {
                i -= (2.0*System.Math.PI);
            }
            return new Complex32((float) System.Math.Log(r), (float) i);
        }

        /// <summary>Returns the sine of a Complex32 number.</summary>
        /// <returns>The sine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the sine of.</param>
        public static Complex32 Sin(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return new Complex32((float) (System.Math.Sin(value.Real)*System.Math.Cosh(value.Imaginary)), (float) (System.Math.Cos(value.Real)*System.Math.Sinh(value.Imaginary)));
        }

        /// <summary>Returns the hyperbolic sine of a Complex32 number.</summary>
        /// <returns>The hyperbolic sine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the hyperbolic sine of.</param>
        public static Complex32 Sinh(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            if (value.Imaginary == 0)
            {
                return new Complex32((float) System.Math.Sinh(value.Real));
            }
            if (value.Real == 0)
            {
                return new Complex32(0, (float) System.Math.Sin(value.Imaginary));
            }
            return new Complex32((float) (System.Math.Sinh(value.Real)*System.Math.Cos(value.Imaginary)), (float) (System.Math.Cosh(value.Real)*System.Math.Sin(value.Imaginary)));
        }

        /// <summary>Returns the square root of a Complex32 number.</summary>
        /// <returns>The square root of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the square root of.</param>
        public static Complex32 Sqrt(Complex32 value)
        {
            Complex32 result;
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }

            if (Complex32.IsInfinity(value))
            {
                return Complex32.Infinity;
            }

            if ((value.Real == 0.0) && (value.Imaginary == 0.0))
            {
                result = Complex32.Zero;
            }
            else
            {
                float x = System.Math.Abs(value.Real);
                float y = System.Math.Abs(value.Imaginary);
                float t;
                if (x >= y)
                {
                    t = y/x;
                    t = (float) (System.Math.Sqrt(x)*System.Math.Sqrt(0.5*(1.0 + System.Math.Sqrt(1.0 + t*t))));
                }
                else
                {
                    t = x/y;
                    t = (float) (System.Math.Sqrt(y)*System.Math.Sqrt(0.5*(t + System.Math.Sqrt(1.0 + t*t))));
                }

                if (value.Real >= 0.0)
                {
                    result = new Complex32(t, value.Imaginary/(2.0f*t));
                }
                else if (value.Imaginary >= 0.0)
                {
                    result = new Complex32(value.Imaginary/(2.0f*t), t);
                }
                else
                {
                    result = new Complex32(-value.Imaginary/(2.0f*t), -t);
                }
            }

            return result;
        }

        /// <summary>Returns the tangent of a Complex32 number.</summary>
        /// <returns>The tangent of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the tangent of.</param>
        public static Complex32 Tan(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            double real = 2.0*value.Real;
            double imag = 2.0*value.Imaginary;
            double denom = System.Math.Cos(real) + System.Math.Cosh(imag);

            return new Complex32((float) (System.Math.Sin(real)/denom), (float) (System.Math.Sinh(imag)/denom));
        }

        /// <summary>Returns the hyperbolic tangent of a Complex32 number.</summary>
        /// <returns>The hyperbolic tangent of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the hyperbolic tangent of.</param>
        public static Complex32 Tanh(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }

            double real = 2.0*value.Real;
            double imag = 2.0*value.Imaginary;
            double denom = System.Math.Cosh(real) + System.Math.Cos(imag);

            return new Complex32((float) (System.Math.Sinh(real)/denom), (float) (System.Math.Sin(imag)/denom));
        }

        /// <summary>Returns the inverse sine of a Complex32 number.</summary>
        /// <returns>The inverse sine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the inverse sine of.</param>
        public static Complex32 Asin(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }

            return sNi32*Log(Complex32.I*value + Sqrt(1 - value*value));
        }

        /// <summary>Returns the inverse cosine of a Complex32 number.</summary>
        /// <returns>The inverse cosine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the inverse cosine of.</param>
        public static Complex32 Acos(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return sNi32*Log(value + Complex32.I*Sqrt(1 - value*value));
        }

        /// <summary>Returns the inverse tangent of a Complex32 number.</summary>
        /// <returns>The inverse tangent of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the inverse tangent of.</param>
        public static Complex32 Atan(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return (1/(Complex32.I*2))*(Log((1 + Complex32.I*value)) - Log((1 - Complex32.I*value)));
        }

        /// <summary>Returns the inverse hyperbolic sine of a Complex32 number.</summary>
        /// <returns>The inverse hyperbolic sine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the inverse hyperbolic sine of.</param>
        public static Complex32 Asinh(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return -Log(Sqrt(1 + value*value) - value);
        }

        /// <summary>Returns the inverse hyperbolic cosine of a Complex32 number.</summary>
        /// <returns>The inverse hyperbolic cosine of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the hyperbolic cosine of.</param>
        public static Complex32 Acosh(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return 2*Log((Sqrt(((value + 1)/2)) + Sqrt((value - 1)/2)));
        }

        /// <summary>Returns the inverse hyperbolic tangent of a Complex32 number.</summary>
        /// <returns>The inverse hyperbolic tangent of the Complex32 number.</returns>
        /// <param name="value">The Complex32 number to compute the hyperbolic tangent of.</param>
        public static Complex32 Atanh(Complex32 value)
        {
            if (Complex32.IsNaN(value))
            {
                return Complex32.NaN;
            }
            return .5f*(Log(1 + value) - Log(1 - value));
        }
    }
}